﻿using MediaFoundation.Misc;
using MediaFoundation.ReadWrite;
using MediaFoundation;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Media;
using System.IO;
using System.Reflection;
using System.Windows.Media.Imaging;
using MediaFoundation.Alt;
using System.Windows.Controls;
using System.Data;

namespace CameraMF.NET6
{
    public class MFDevice : IDisposable
    {
        private IMFActivate mActivator;
        private string mFriendlyName;
        private string mSymbolicName;
        static HResult Result;
        public MFDevice(IMFActivate a)
        {
            mActivator = a;
            mFriendlyName = null;
            mSymbolicName = null;
        }

        public IMFActivate Activator
        {
            get { return mActivator; }
        }

        public string Name
        {
            get
            {
                if (mFriendlyName == null)
                {
                    Result = mActivator.GetAllocatedString(MFAttributesClsid.MF_DEVSOURCE_ATTRIBUTE_FRIENDLY_NAME, out mFriendlyName, out int size);
                }
                return mFriendlyName;
            }
        }

        public string SymbolicName
        {
            get
            {
                if (mFriendlyName == null)
                {
                    int size = 0;
                    Result = mActivator.GetAllocatedString(MFAttributesClsid.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_SYMBOLIC_LINK, out mSymbolicName, out size);
                }
                return mSymbolicName;
            }
        }

        public static MFDevice[] GetVideoCaptureDevices()
        {
            IMFActivate[] devices;
            IMFAttributes attrib;
            Result = MFExtern.MFCreateAttributes(out attrib, 1);
            attrib.SetGUID(MFAttributesClsid.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, CLSID.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
            int devicesCount = 0;
            Result = MFExtern.MFEnumDeviceSources(attrib, out devices, out devicesCount);
            MFDevice[] mfdevices = new MFDevice[devicesCount];
            for (int i = 0; i < devicesCount; i++)
            {
                mfdevices[i] = new MFDevice(devices[i]);
            }
            if (attrib != null)
            {
                Marshal.ReleaseComObject(attrib);
            }
            return mfdevices;
        }

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        // IDisposable
        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 释放托管状态(托管对象)。
                    if (mActivator != null)
                    {
                        Marshal.ReleaseComObject(mActivator);
                        mActivator = null;
                    }
                    mFriendlyName = null;
                    mSymbolicName = null;
                    GC.SuppressFinalize(this);
                }

                // TODO: 释放未托管资源(未托管对象)并在以下内容中替代 Finalize()。
                // TODO: 将大型字段设置为 null。
                disposedValue = true;
            }
        }

        // TODO: 仅当以上 Dispose(disposing As Boolean)拥有用于释放未托管资源的代码时才替代 Finalize()。
        // ~MFDevice()
        // {
        //     // 请勿更改此代码。将清理代码放入以上 Dispose(disposing As Boolean)中。
        //     Dispose(false);
        // }

        // Visual Basic 添加此代码以正确实现可释放模式。

        public void Dispose()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(disposing As Boolean)中。
            Dispose(true);
            // TODO: 如果在以上内容中替代了 Finalize()，则取消注释以下行。
            // GC.SuppressFinalize(this);
        }
        #endregion
    }
    public class MFCaptureAsync : COMBase, IMFSourceReaderCallback, IDisposable
    {
        public EventHandler<BitmapSource> OnCaptureEvent;
        HResult _Result;
        private HResult Result
        {
            get { return _Result; }
            set
            {
                _Result = value;
                if (value != HResult.S_OK)
                    Console.WriteLine("Error");
            }
        }
        private IMFSourceReaderAsync mSourceReaderAsync;
        //private IMFMediaType mMediatype;
        //private IntPtr bufptr;
        //private byte[] jpgbuf;
        //private Stream stream;
        private JpegBitmapDecoder jpgdecoder;
        private BitmapSource cameraframe;
        private System.Windows.Threading.Dispatcher dispatcher;

        public MFCaptureAsync(System.Windows.Threading.Dispatcher dispatcher)
        {
            MFExtern.MFStartup(0x20070, MFStartup.Lite);
            this.dispatcher = dispatcher;
        }

        public void SetDevice(MFDevice device, int width, int height, int fps, string fmt)
        {
            IMFActivate activate = device.Activator;
            IMFMediaSource mediaSource = null;
            IMFAttributes attrib = null;
            lock (this)
            {
                try
                {
                    CloseDevice();
                    Result = MFExtern.MFCreateAttributes(out attrib, 1);
                    //Result = attrib.SetGUID(MFAttributesClsid.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE, CLSID.MF_DEVSOURCE_ATTRIBUTE_SOURCE_TYPE_VIDCAP_GUID);
                    Result = activate.ActivateObject(typeof(IMFMediaSource).GUID, out object ppv);
                    //if (ppv is IMFMediaSource dediaSource)
                    mediaSource =(IMFMediaSource)ppv;
                    var mSymbolicLink = device.SymbolicName; //devicePath

                    Result = mediaSource.CreatePresentationDescriptor(out IMFPresentationDescriptor ppPresentationDescriptor);
                    Result = ppPresentationDescriptor.GetStreamDescriptorByIndex(0, out bool select, out IMFStreamDescriptor imfs);
                    Result = imfs.GetMediaTypeHandler(out IMFMediaTypeHandler ppMediaTypeHandler);
                    Result = ppMediaTypeHandler.GetMediaTypeCount(out int mediaTypeCount);
                    for (int c = 0; c < mediaTypeCount; c++)
                    {
                        ppMediaTypeHandler.GetMediaTypeByIndex(c, out IMFMediaType mediaType );
                        Guid subtype;
                        mediaType.GetGUID(MFAttributesClsid.MF_MT_SUBTYPE, out subtype);
                        //mediaType.SetGUID(MFAttributesClsid.MF_MT_SUBTYPE, subtype);
                        var name = NameofGUID(subtype);
                        long framesize;
                        mediaType.GetUINT64(MFAttributesClsid.MF_MT_FRAME_SIZE, out framesize);
                        uint w, h;
                        w = (uint)(framesize >> 32);
                        h = (uint)(framesize & 0xFFFFFFFL);
                        long framerate;
                        mediaType.GetUINT64(MFAttributesClsid.MF_MT_FRAME_RATE, out framerate);
                        int frame = (int)(framerate >> 32);
                        int ratio = (int)(framerate & 0xFFFFFFFFL);
                        int samplesize;
                        mediaType.GetUINT32(MFAttributesClsid.MF_MT_SAMPLE_SIZE, out samplesize);
                        if (w == width && h == height && name == fmt)
                        { 
                            Result = ppMediaTypeHandler.SetCurrentMediaType(mediaType);
                            break;
                        }
                    }

                    Result = attrib.SetUnknown(MFAttributesClsid.MF_SOURCE_READER_ASYNC_CALLBACK, this);
                    IMFSourceReader sourcereader;
                    Result = MFExtern.MFCreateSourceReaderFromMediaSource(mediaSource, attrib, out sourcereader);
                    mSourceReaderAsync = (IMFSourceReaderAsync)sourcereader;
                    {
                        //int i = 0;
                        //do
                        //{
                        //    Result = mSourceReaderAsync.GetNativeMediaType((int)MF_SOURCE_READER.FirstVideoStream, i, out IMFMediaType mediatype);
                        //    if (Failed(Result))
                        //    {
                        //        break;
                        //    }
                        //    try
                        //    {
                        //        Result = mediatype.GetUINT64(MFAttributesClsid.MF_MT_FRAME_SIZE, out long framesize);
                        //        Result = mediatype.GetUINT64(MFAttributesClsid.MF_MT_FRAME_RATE, out long framerate);
                        //        Result = mediatype.GetGUID(MFAttributesClsid.MF_MT_SUBTYPE, out Guid subtype);

                        //        uint w, h;
                        //        w = (uint)(framesize >> 32);
                        //        h = (uint)(framesize & 0x00000000FFFFFFFFL);
                        //        int frame = (int)(framerate >> 32);
                        //        int ratio = (int)(framerate & 0x00000000FFFFFFFFL);
                        //        var name = NameofGUID(subtype);
                        //        Console.WriteLine( w +" " + h + " " +frame + " " + name);
                        //        Result = TryMediaType(ref mediatype, width, height, fps, fmt);
                        //        if (Result == HResult.S_OK)
                        //        {
                        //            //mMediatype = mediatype;
                        //            break;
                        //        }
                        //    }
                        //    catch (Exception ex)
                        //    {
                        //        Console.WriteLine(ex.ToString());
                        //    }
                        //    finally
                        //    {
                        //        SafeRelease(mediatype);
                        //    }
                        //    i++;
                        //} while (true);
                    }
                    Result = mSourceReaderAsync.ReadSample((int)MF_SOURCE_READER.FirstVideoStream, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
                    if (Failed(Result))
                    {
                        if (mediaSource != null)
                        {
                            mediaSource.Shutdown();
                        }
                        CloseDevice();
                    }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
                finally
                {
                    SafeRelease(mediaSource);
                    SafeRelease(attrib);
                }
            }
        }

        public HResult TryMediaType(ref IMFMediaType mediaType, int width, int height, int fps, string name)
        {
            //ulong framesize;
            //ulong framerate;
            Guid subtype;
            Result = mediaType.GetUINT64(MFAttributesClsid.MF_MT_FRAME_SIZE, out long framesize);
            Result = mediaType.GetUINT64(MFAttributesClsid.MF_MT_FRAME_RATE, out long framerate);
            Result = mediaType.GetGUID(MFAttributesClsid.MF_MT_SUBTYPE, out subtype);

            uint w, h;
            w = (uint)(framesize >> 32);
            h = (uint)(framesize & 0x00000000FFFFFFFFL);
            int frame = (int)(framerate >> 32);
            int ratio = (int)(framerate & 0x00000000FFFFFFFFL);

            int samplesize;
            Result = mediaType.GetUINT32(MFAttributesClsid.MF_MT_SAMPLE_SIZE, out samplesize);
            if (Result != HResult.S_OK)
                return Result;
            Console.WriteLine(w.ToString() + " x " + h.ToString() + " @ " + ((double)frame / ratio).ToString("f1") + "hz"
                    + "\t" + "samplesize:" + samplesize.ToString()
                    + "\t" + "type:" + NameofGUID(subtype));
            if (w == width && h == height && (double)frame / ratio == fps && NameofGUID(subtype) == name)
            {
                //Result = mSourceReaderAsync.GetNativeMediaType((int)MF_SOURCE_READER.FirstVideoStream, new MFInt(0), out IMFMediaType pptype);
                //Result = mSourceReaderAsync.GetCurrentMediaType((int)MF_SOURCE_READER.FirstVideoStream, out IMFMediaType ppMediaType);
                //var mfint = new MFInt(0);
                //mediaType = pptype; 
                Result = mSourceReaderAsync.SetCurrentMediaType((int)MF_SOURCE_READER.FirstVideoStream, new MFInt(0), mediaType);

                mediaType.GetUINT32(MFAttributesClsid.MF_MT_SAMPLE_SIZE, out samplesize); // 获得一个样本的大小
                //bufptr = Marshal.AllocHGlobal(samplesize); // 设置图片的Buffer大小
                return Result;
            }
            return HResult.S_FALSE;
        }

        private string NameofGUID(Guid guid)
        {
            FieldInfo[] names = typeof(MFMediaType).GetFields();
            foreach (FieldInfo i in names)
            {
                object obj = i.GetValue(null);
                if (obj is Guid && (Guid)obj == guid)
                {
                    return i.Name;
                }
            }
            return "unknown";
        }

        public void CloseDevice()
        {
            lock (this)
            {
                SafeRelease(mSourceReaderAsync);
                mSourceReaderAsync = null;
            }
        }

        // 哪个线程创建的mSourceReaderAsync，就哪个线程请求，其他线程不可。
        public void Request()
        {
            Result = mSourceReaderAsync.ReadSample((int)MF_SOURCE_READER.FirstVideoStream, 0, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);
        }

       

        #region IDisposable Support
        private bool disposedValue = false; // 要检测冗余调用

        // IDisposable
        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    // TODO: 释放托管状态(托管对象)。
                }

                // TODO: 释放未托管资源(未托管对象)并在以下内容中替代 Finalize()。
                // TODO: 将大型字段设置为 null。
                disposedValue = true;
            }
        }

        // TODO: 仅当以上 Dispose(disposing As Boolean)拥有用于释放未托管资源的代码时才替代 Finalize()。
        ~MFCaptureAsync()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(disposing As Boolean)中。
            Dispose(false);
        }

        // Visual Basic 添加此代码以正确实现可释放模式。
        public void Dispose()
        {
            // 请勿更改此代码。将清理代码放入以上 Dispose(disposing As Boolean)中。
            Dispose(true);
            // TODO: 如果在以上内容中替代了 Finalize()，则取消注释以下行。
            // GC.SuppressFinalize(this);
        }
        #endregion


        #region ******************************   IMFSourceReaderCallback 接口   ******************
        public HResult OnReadSample(HResult hrStatus, int dwStreamIndex, MF_SOURCE_READER_FLAG dwStreamFlags, long llTimestamp, IMFSample pSample)
        {
            lock (this)
            {
                IMFMediaBuffer mediabuffer = null;
                try
                {
                    if (Succeeded(hrStatus))
                    {
                        if (pSample != null)
                        {
                            Result = pSample.GetBufferByIndex(0, out mediabuffer);
                        }
                        else
                        {
                            goto done;
                        }
                    }
                    else
                    {
                        goto done;
                    }

                    int maxlen, curlen; // 图像大小max，jpg的大小cur，压缩jpg大小不定
                    mediabuffer.GetMaxLength(out maxlen);
                    mediabuffer.GetCurrentLength(out curlen);
                    IntPtr pubffPtr = IntPtr.Zero;
                    mediabuffer.Lock(out pubffPtr, out maxlen, out curlen); // 锁定 MediaBuffer
                    var jpgbuf = new byte[maxlen];
                    Marshal.Copy(pubffPtr, jpgbuf, 0, curlen); // 从MediaBuffer拷贝到自定的图片buffer
                    //if (stream == null)
                    //{
                    //    //stream = new MemoryStream(jpgbuf, maxlen);
                    //    stream = new MemoryStream(jpgbuf, 0, maxlen);
                    //}
                    var stream = new MemoryStream(jpgbuf, 0, maxlen);
                    // 转换buffer成BitmapSource
                    //stream.Seek(0, SeekOrigin.Begin)
                    //stream.Write(jpgbuf, 0, curlen)'在初始化设定数组，避免再写
                    //stream.Flush();
                    //stream.Seek(0, SeekOrigin.Begin); // 重新回到头部
                    try
                    {
                        if (jpgdecoder == null)
                        {
                            jpgdecoder = new JpegBitmapDecoder(stream, BitmapCreateOptions.None, BitmapCacheOption.OnLoad); // OnLoad很重要
                        }
                        BitmapFrame Frame = jpgdecoder.Frames[0];
                        cameraframe = Frame;
                        dispatcher.Invoke(() =>
                        {
                            OnCaptureEvent?.Invoke(this, cameraframe);
                        });
                    }
                    catch (Exception)
                    {
                    }

                    jpgdecoder = null;

                    // stream.Close(); // 关闭摄像头时关闭流，不是每帧读完就关闭
                    mediabuffer.Unlock();
                done: { }
                }
                catch (Exception ex)
                {
                    Console.WriteLine(ex.ToString());
                }
                finally
                {
                    SafeRelease(mediabuffer);
                    SafeRelease(pSample);
                }
            }
            return HResult.S_OK;
        }
        public HResult OnFlush(int dwStreamIndex)
        {
            return HResult.S_OK;
        }

        public HResult OnEvent(int dwStreamIndex, IMFMediaEvent pEvent)
        {
            return HResult.S_OK;
        }
        #endregion
    }
}
